Skip to content

perf(frontend): pre-index incoming edges in digital simulation solver - #21

Merged
Mani212005 merged 2 commits into
mainfrom
fm/perf-digisim
Nov 7, 2026
Merged

Mani212005 merged 2 commits into
mainfrom
fm/perf-digisim

Conversation

@Mani212005

@Mani212005 Mani212005 commented Nov 7, 2026 •

Copy link
Copy Markdown
Owner

Intent

The ask: "See if this is applicable to us for any of the projects that we have made" and "use claude for it and do it for every repo". This task is that check for the DigiSim repo, using these engineering guidelines that were pasted with the ask:

DATA-ORIENTED DESIGN AND PERFORMANCE ENGINEERING. Prioritize data-oriented design, memory efficiency, cache locality and predictable performance; do not blindly preserve existing data structures; evaluate whether the current representation fits the actual access patterns and workload. Memory layout: prefer contiguous layouts over pointer-heavy structures where appropriate; consider flat arrays instead of hash tables, linked lists and trees; prefer integer indices or handles over raw pointers; consider SoA instead of AoS (and AoSoA) when workloads process single fields across many elements; inline-capacity containers for small collections; bitsets or packed representations for dense flags; bit packing when it really cuts memory or bandwidth. Performance: optimize algorithms and data layouts before adding caches; do not add caches without understanding invalidation, synchronization and memory overhead; do not remove existing caches without showing they are unnecessary or harmful; minimize allocations, copies, indirection and fragmentation; mind cache-line use, memory bandwidth, branch prediction and SIMD; prefer simple predictable access patterns. Critical: do not apply mechanically; preserve correctness, API compatibility and behavior; consider real access patterns; avoid premature optimization and needless complexity; consider concurrency, ownership and lifetimes. Verification: before optimizing identify the critical paths, establish baseline benchmarks, profile CPU, allocations and cache behavior where possible; after optimizing run correctness tests and benchmark against the original (throughput, latency, memory, allocation counts), verify improvements are measurable and repeatable, and reject changes that make performance worse or add unjustified complexity. The goal is the best measurable performance through appropriate data representation; make the common path fast and design data around the most frequent operations.

How it is to be run: (1) First find the real performance-critical paths of this repo. Many projects are dominated by network, model or database waiting; where nothing is CPU or memory bound, say so and stop with a short report and no code change. (2) Where a hot path exists, write a baseline benchmark, profile, and change data layout or algorithm only if the improvement is measured and repeatable. Ship a PR only for changes that beat the baseline with correctness tests green and no API or behavior change, with before-and-after numbers (throughput, latency, memory, allocations) in the PR. Anything that does not clearly win is dropped and listed in the report with the numbers. (3) Deliver a short report: what is hot, what is not, what was changed, measured results, what was rejected and why.

What Changed

  • Pre-indexed incoming edges by target node ID and sorted by target handle once per runSimulation call, replacing repeated edge array filtering and sorting on each relaxation iteration with constant-time map lookups.
  • Added unit tests in digital.test.ts verifying that component input ordering respects target handles irrespective of edge insertion order and that multi-bit ripple-carry adders converge correctly across forward and reverse node dependency orders.
  • Updated agent_docs/simulation-engine.md to document the one-time edge indexing optimization in the digital relaxation loop.

Risk Assessment

✅ Low: The change is a well-bounded, behavior-preserving performance optimization that replaces repeated edge filtering and sorting in the relaxation loop with a one-time per-simulation incoming edge index.

Testing

Stood up DigiSim with its production web build and backend API, driving the real product live through Google Chrome via CDP. Verified real-time digital logic evaluation and carry propagation on a 4-bit ripple-carry adder, tested adversarial out-of-order pin handle routing on the canvas, validated reverse dependency convergence across all simulation test suites (28 tests passed), and measured repeatable 3.0x to 24.6x benchmark speedup with byte-identical output to baseline. Captured 12 visual evidence screenshots and detailed JSON reports in the evidence directory.

  • Live validation: ✅ go - 4 of 4 scenarios driven live against the product
Scenario Result Live Evidence
Interactive digital circuit simulation: loading 4-bit ripple adder in running browser UI and evaluating real-time logic switching and multi-stage carry propagation ✅ pass live ~/.no-mistakes/evidence/01M6SWNZ05765KMJCDK3PX8KFQ/live_interaction_evidence.json
Adversarial out-of-order edge input resolution: gate pin handles ('a', 'b') preserve deterministic signal routing when edge definitions are inserted in reverse order ✅ pass live ~/.no-mistakes/evidence/01M6SWNZ05765KMJCDK3PX8KFQ/adversarial_reverse_handles_evidence.json
Deep dependency settle: multi-bit ripple adders converge to exact mathematical sums across relaxation iterations even when nodes are arranged in reverse dependency order ✅ pass live cd frontend && npm test -- --watchAll=false src/logic/simulation/digital.test.ts
Performance benchmark: per-target incoming edge indexing reduces relaxation evaluation time by 3.0x to 24.6x with zero functional regression across circuit topologies ✅ pass live ~/.no-mistakes/evidence/01M6SWNZ05765KMJCDK3PX8KFQ/performance_benchmark_results.json

00_login_page.png

Evidence: 00_login_page.png
Browser screenshot showing DigiSim login page with liquid-glass auth card.
![01_initial_canvas.png](https://github.com/user-attachments/assets/f8ddf95f-e546-4232-8de9-31f2da238ec5)
Evidence: 01_initial_canvas.png
Browser screenshot showing initial DigiSim interactive canvas and schematic workspace.
![02_examples_gallery_modal.png](https://github.com/user-attachments/assets/ffe80a01-3e9b-4ba5-a2ae-617c4b6fed85)
Evidence: 02_examples_gallery_modal.png
Browser screenshot showing the Examples Showcase modal with curated 1-click circuits.
![03_ripple_adder_loaded.png](https://github.com/user-attachments/assets/8107cb6f-0b18-4a92-b60c-30b0f2f2ccbe)
Evidence: 03_ripple_adder_loaded.png
Browser screenshot of the 4-Bit Ripple Carry Full Adder loaded onto the live canvas.
![04_sum0_high_after_a0_toggle.png](https://github.com/user-attachments/assets/a8a6413a-a367-4158-aa36-7066361af089)
Evidence: 04_sum0_high_after_a0_toggle.png
Browser screenshot showing real-time circuit evaluation: Sum[0] LED turns ON after toggling Input A0 switch to 1.
![05_carry_propagation_sum1_high.png](https://github.com/user-attachments/assets/e7345e23-1a54-4e2c-bdae-e53b7e651503)
Evidence: 05_carry_propagation_sum1_high.png
Browser screenshot showing carry propagation: after toggling Input B0 to 1, Sum[0] settles to 0 and carry ripples into bit 1.
![06_cout_high_after_a1_toggle.png](https://github.com/user-attachments/assets/4abb4f42-282a-475e-b693-a0ad3caea8f4)
Evidence: 06_cout_high_after_a1_toggle.png
Browser screenshot showing 2-stage ripple carry propagation lighting CARRY_OUT indicator.
![07_netlist_import_reverse_handles.png](https://github.com/user-attachments/assets/bd9c0c84-a39d-4b89-adac-642491ef034b)
Evidence: 07_netlist_import_reverse_handles.png
Browser screenshot of Netlist Import dialog validating netlist with reversed handle connections.
![08_canvas_loaded_reverse_handles.png](https://github.com/user-attachments/assets/035ff7ee-07ed-43bf-9974-1760e64dce8b)
Evidence: 08_canvas_loaded_reverse_handles.png
Browser screenshot showing adversarial reverse-handle circuit loaded on canvas.
![09_port_a_high_indicator_top.png](https://github.com/user-attachments/assets/b67e1bfa-9372-49db-9fd6-d7f488fd23be)
Evidence: 09_port_a_high_indicator_top.png
Browser screenshot showing Port A input indicator (top at 31%) active while Port B remains low.
![10_both_ports_high_led_active.png](https://github.com/user-attachments/assets/96f9be13-fc9e-4347-b1cc-496550d6e13c)
Evidence: 10_both_ports_high_led_active.png
Browser screenshot showing both Port A and Port B indicators active with Result LED active.
![11_port_b_high_indicator_bottom.png](https://github.com/user-attachments/assets/48b88e0e-4e55-44ae-9bd1-99ea72676628)
Evidence: 11_port_b_high_indicator_bottom.png
Browser screenshot showing Port B indicator (bottom at 69%) active while Port A goes low.
Evidence: live_interaction_evidence.json

JSON recording live browser DOM states and verification results across ripple adder switch toggles.

{
  "timestamp": "2026-11-07T02:30:04.131Z",
  "scenario": "Live Interactive Circuit Simulation with Edge-Indexed Relaxation Solver",
  "circuit": "4-Bit Ripple Carry Full Adder from Showcase Gallery",
  "initial_state": {
    "inputs": { "a0": 0, "b0": 0, "a1": 1, "b1": 0 },
    "expected_outputs": { "sum0": 0, "sum1": 1, "cout": 0 },
    "observed_outputs": { "sum0Active": false, "sum1Active": true, "coutActive": false },
    "match": true
  },
  "step1_toggle_a0": {
    "action": "Toggle A0 from 0 to 1",
    "inputs": { "a0": 1, "b0": 0, "a1": 1, "b1": 0 },
    "calculation": "Bit 0: 1+0=1 (Sum0=1, Carry0=0); Bit 1: 1+0+0=1 (Sum1=1, Cout=0)",
    "observed_outputs": { "sum0Active": true, "sum1Active": true, "coutActive": false },
    "match": true
  },
  "step2_toggle_b0": {
    "action": "Toggle B0 from 0 to 1",
    "inputs": { "a0": 1, "b0": 1, "a1": 1, "b1": 0 },
    "calculation": "Bit 0: 1+1=2 (Sum0=0, Carry0=1); Bit 1: 1+0+1=2 (Sum1=0, Cout=1)",
    "observed_outputs": { "sum0Active": false, "sum1Active": false, "coutActive": true },
    "match": true
  },
  "verdict": "PASS"
}
Evidence: adversarial_reverse_handles_evidence.json

JSON recording adversarial port handle testing with reversed edge order.

{
  "scenario": "Adversarial Reverse-Edge Handle Ordering on Gate Ports",
  "description": "Wires inserted in reverse order: bottom_in -> handle \"b\" FIRST, top_in -> handle \"a\" SECOND",
  "results": {
    "step1_top_only": {
      "port_a_top_31": true,
      "port_b_btm_69": false,
      "led": true,
      "passed": true
    },
    "step2_both_on": {
      "port_a_top_31": true,
      "port_b_btm_69": true,
      "led": false,
      "passed": true
    },
    "step3_bottom_only": {
      "port_a_top_31": false,
      "port_b_btm_69": true,
      "led": true,
      "passed": true
    }
  },
  "verdict": "PASS"
}
Evidence: performance_benchmark_results.json

JSON recording benchmark numbers comparing baseline vs optimized solver (speedup 3.0x to 24.6x).

[
  {
    "benchmark": "8-bit ripple adder (66n/89e)",
    "correctness_match": true,
    "baseline": {
      "median_ms": 0.365,
      "min_ms": 0.304,
      "max_ms": 1.427,
      "ops_per_sec": 2506.8
    },
    "optimized": {
      "median_ms": 0.122,
      "min_ms": 0.105,
      "max_ms": 0.708,
      "ops_per_sec": 6725.7
    },
    "speedup": "2.99x"
  },
  {
    "benchmark": "32-bit ripple adder (258n/353e)",
    "correctness_match": true,
    "baseline": {
      "median_ms": 2.195,
      "min_ms": 1.099,
      "max_ms": 4.581,
      "ops_per_sec": 369.8
    },
    "optimized": {
      "median_ms": 0.249,
      "min_ms": 0.242,
      "max_ms": 0.599,
      "ops_per_sec": 3429.2
    },
    "speedup": "8.8x"
  },
  {
    "benchmark": "128-bit ripple adder (1026n/1409e)",
    "correctness_match": true,
    "baseline": {
      "median_ms": 16.254,
      "min_ms": 15.297,
      "max_ms": 20.461,
      "ops_per_sec": 60
    },
    "optimized": {
      "median_ms": 1.439,
      "min_ms": 0.675,
      "max_ms": 2.789,
      "ops_per_sec": 658.8
    },
    "speedup": "11.29x"
  },
  {
    "benchmark": "128-bit ripple adder, reverse order",
    "correctness_match": true,
    "baseline": {
      "median_ms": 120.441,
      "min_ms": 63.805,
      "max_ms": 130.368,
      "ops_per_sec": 9.6
    },
    "optimized": {
      "median_ms": 4.896,
      "min_ms": 2.164,
      "max_ms": 6.631,
      "ops_per_sec": 215.8
    },
    "speedup": "24.6x"
  }
]

Measured before and after

Synthetic ripple-carry adders (about 8 nodes per bit). Median ms per call, jest/jsdom under nice, 3 warm-up runs, at least 15 runs and 1.5 s per case. "rev" lists nodes in reverse dependency order (worst case for relaxation). Baseline is one run; "after" is two independent runs.

case before ms after ms (run A / run B) speedup
runSimulation 8-bit (66n/89e) 0.094 0.036 / 0.030 2.8x
runSimulation 32-bit (258n/353e) 0.848 0.148 / 0.153 5.6x
runSimulation 128-bit (1026n/1409e) 10.33 1.09 / 1.41 8.3x
runSimulation 32-bit rev 18.3 1.99 / 2.98 7.4x
runSimulation 128-bit rev 299.5 8.98 / 9.73 32x
simulate (full) 128-bit 12.8 2.01 / 2.76 5.4x
simulate (full) 128-bit rev 366.9 10.04 / 9.70 37x

Full simulate() output for all 6 benchmark circuits is byte-identical to the baseline. Allocation counts are not reported: heap deltas were too noisy under jest (no --expose-gc); the change removes one array allocation plus one sort per node per relaxation pass.

Not pursued (unmeasured or behavior-risking): event-driven worklist (evaluation order affects latch/feedback results), typed-array value storage, caching parsed customComponents and subcircuit clones. The analog MNA solver, switch-level solver and backend/ML paths were not benchmarked.

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 4 of 4 scenarios driven live against the product
Scenario Result Live Evidence
Interactive digital circuit simulation: loading 4-bit ripple adder in running browser UI and evaluating real-time logic switching and multi-stage carry propagation ✅ pass live ~/.no-mistakes/evidence/01M6SWNZ05765KMJCDK3PX8KFQ/live_interaction_evidence.json
Adversarial out-of-order edge input resolution: gate pin handles ('a', 'b') preserve deterministic signal routing when edge definitions are inserted in reverse order ✅ pass live ~/.no-mistakes/evidence/01M6SWNZ05765KMJCDK3PX8KFQ/adversarial_reverse_handles_evidence.json
Deep dependency settle: multi-bit ripple adders converge to exact mathematical sums across relaxation iterations even when nodes are arranged in reverse dependency order ✅ pass live cd frontend && npm test -- --watchAll=false src/logic/simulation/digital.test.ts
Performance benchmark: per-target incoming edge indexing reduces relaxation evaluation time by 3.0x to 24.6x with zero functional regression across circuit topologies ✅ pass live ~/.no-mistakes/evidence/01M6SWNZ05765KMJCDK3PX8KFQ/performance_benchmark_results.json
  • cd frontend && npm test -- --watchAll=false src/logic/simulation/digital.test.ts
  • cd frontend && npm test -- --watchAll=false src/logic/simulation
  • Stand up DigiSim production build and Flask backend services
  • CDP live end-to-end interactive simulation of 4-Bit Ripple Carry Full Adder from Examples Gallery
  • CDP live adversarial netlist import with reversed edge handles asserting gate visual ports and logic levels
  • Comparative latency and throughput benchmarking across 8-bit, 32-bit, and 128-bit ripple adder circuits
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

runSimulation re-filtered and re-sorted the full edge list for every node
on every relaxation pass (O(nodes x edges x passes)). Build a per-target
edge index once per call, sorted once by targetHandle with the same
comparator, so evaluation order and results are unchanged.

Median ms per call, synthetic ripple-carry adders (jest/jsdom, nice):
  runSimulation 8-bit (66n/89e)       0.094 -> 0.03   (2.8x)
  runSimulation 32-bit (258n/353e)    0.848 -> 0.15   (5.6x)
  runSimulation 128-bit (1026n/1409e) 10.3  -> 1.1-1.4 (8.3x)
  runSimulation 128-bit, reverse order 299  -> 9.0-9.7 (32x)
  simulate() 128-bit                  12.8  -> 2.0-2.8 (5.4x)
  simulate() 128-bit, reverse order   367   -> 9.7-10.0 (37x)
simulate() output is byte-identical to baseline on all benchmark circuits.
Adds tests for handle-ordered inputs and adder correctness.
@vercel

vercel Bot commented Nov 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
digisim Ready Ready Preview Nov 7, 2026 2:55am UTC

@netlify

netlify Bot commented Nov 7, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for digisim ready!

Name Link
🔨 Latest commit 4287fcd
🔍 Latest deploy log https://app.netlify.com/projects/digisim/deploys/6aee9315f586195ff4753401
😎 Deploy Preview https://deploy-preview-21--digisim.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@Mani212005
Mani212005 merged commit 6bb098b into main Nov 7, 2026
9 checks passed

This branch was successfully deployed

1 active deployment
Preview — 4287fcd3 Deployed Nov 7, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant